Unity实用框架(一)场景管理框架

您所在的位置:网站首页 unity 加载场景 Unity实用框架(一)场景管理框架

Unity实用框架(一)场景管理框架

2023-09-12 06:52| 来源: 网络整理| 查看: 265

文章目录 Unity实用框架(一)场景管理框架框架思路IScene/ISceneManagerISceneISceneManager UISceneManagerPopPush

Unity实用框架(一)场景管理框架

众所周知,Unity引擎本身提供了具有切换场景功能的SceneManager模块,但只包含比较基础的功能,比如简单的切换场景、创建场景等,想要使得我们的场景管理框架能够适用于更加复杂的情形,显然需要一套更加强大的接口。下面,笔者将提供一种撰写灵活好用的SceneManager的可行思路。

框架思路 IScene/ISceneManager

首先,为了践行面向接口编程的思想,我们需要理解我们想要达成怎样的目标,并以接口的形式将其展现出来。定义Iscene与ISceneManager以抽象场景对象和场景管理组件对象。

定义这两个接口后,在之后的实际实现中,就可以分别使新的类型实现IScene接口称为一个可加载的场景,或实现ISceneManager使得类型拥有以某种逻辑完成场景切换功能的工具(如切换UI场景的UISceneManager或切换战斗场景的BattleSceneManager)。

IScene

一个典型的场景能够定义为一组状态机,在这里,为场景定义如下状态:

场景加载 Load Begin Resume 场景释放 Pause Finish 场景暂停 Pause Resume

可以看到,这里将场景分为三条线,五个状态,还是比较简单的。这五个状态基本上能够满足场景管理的需求。对应上面每一个状态的,是一个函数,该函数执行状态切换时应当完成的操作。当然,由于load状态要负责场景资源的加载,我们自然而然地将其返回值定义为IEnumerator以应用协程。

IEnumerator Load(object savedState);

此外,在接口中定义两个属性,一个为name(string)以标识场景,另一个为ISceneManager接口,此成员应当作为当前SceneManager单例的引用,用于完成Scene与SceneManager间的交互。

IScene的完整定义如下:

public interface IScene { string Name { get; } ISceneManager SceneManager { get; set; } IEnumerator Load(object savedState); void Begin(); void Resume(); void Pause(); void Finish(); } ISceneManager

在这里,我们在SceneManager使用栈结构来储存场景。当然这并非编写场景管理框架的唯一解决方案,但使用栈自然有它的好处所在。进入下一个页面/回到上一个页面是场景切换中经常出现的模式,使用栈结构能很好的适应这种情况。

既然是使用栈,那么ISceneManager自然就应当定义Push和Pop两种状态,当然,为了更加灵活地处理场景切换,添加replace状态,该状态相当于pop和push的组合,但省去了中间状态。

同时,对于场景切换的中间态,我们单独用一个类来定义它:

public class Transition

这个“中间态”类将极大地扩展我们的SceneManager的可用性,它包含了前一个场景、后一个场景,以及场景切换的动态效果,此类并非实际实现,想要实现类似渐入渐出等效果,应当撰写此类的子类。

public enum TransitionType { Push, Pop, Replace } public class Transition { public TransitionType TransitionType { get; set; } //转换类型 public IScene NextScene { get; set; } //下一个场景 public Type NextSceneType { get; set; } //下一个场景的类型 public object SavedState { get; set; } //保存的场景数据 public event Action TransitionEnded; // 回调事件 public event Action TransitionFadeOuted; // 回调事件 internal void RaiseTransitionEnded() { TransitionEnded?.Invoke(); } internal void RaiseTransitionFadeOuted() { TransitionFadeOuted?.Invoke(); } }

最后,在ISceneManager接口中,包含了当前正在运行的场景、当前指定的Transition,切换场景的方法和回调事件。

public interface ISceneManager { IScene CurrentScene { get; } Transition CurrentTransition { get; } event Action TransitionStarted; event Action TransitionEnded; void SetTransition(Transition transition);//可理解为原生SceneManager的loadscene函数。 } UISceneManager

现在,我们来完成IsceneManager的一个实际实现。不同种类的场景可能需要应用不同的SceneManager(都实现ISceneManager接口),这里以UI为例。

首先,是UI界面需要的一些组件:

private UIRoot uiRoot; //NGUI相关,如果使用UGUI或者其他GUI控件就不需要它 private UICamera uiCamera; //拍摄UI界面的摄像机 private LayerMask eventReceiverMask = -1; //层级遮罩

然后,需要一个栈储存当前加载了的所有场景

private List stackedOverlayList = new List(); private List stackedScenes = new List();

其中,上面的IUIOverlay接口定义了当前UI界面上的其他UI,或者说画中画,IUIOverlay本身不在本文的讨论之列。列在这里的目的在于,读者可以清楚如果想用SceneManager实现画中画、弹窗等效果,可以让弹窗、画中画的控制脚本实现此接口,以方便在SceneManager中统一管理。

当然,我们的重点在于SetTransition函数实现。

public void SetTransition(Transition transition) { if (CurrentTransition != null) { return; } var t = transition as UITransition; //UITransition是Transition的子类 if (t.Animation == TransitionAnimation.None) { CoroutineManager.Instance.StartCoroutine(UIRoot, SetTransitionAsync(transition)); } else if (t.Animation == TransitionAnimation.Fade) { CoroutineManager.Instance.StartCoroutine(UIRoot, SetFadeTransitionAsync(transition)); } else { CoroutineManager.Instance.StartCoroutine(UIRoot, SetSlideTransitionAsync(transition)); } }

不同的场景转换效果(None无效果,Fade渐入渐出,Slide幻灯片式滑出,这里假设只有这三种方式)需要不同的转换函数,因此转交给不同的函数进行调用。注意场景转换这种开销明显较大的操作应当应用协程。这里的CoroutineManager也并非原生的协程管理器,在其它文章中,笔者将会介绍一个优雅的CoroutineManager将如何设计。

场景转换时要做些什么呢?首先,需要判断当前的转换方式时Push还是Pop,以此判断是从stackedScene中取出场景还是压入场景。

Pop

对于pop,所要做的就是弹出当前场景,并从存于栈中的场景中取出一个并加载。

if (transition.TransitionType == TransitionType.Pop) { if (stackedScenes.Count > 0) { var kv = stackedScenes[stackedScenes.Count - 1]; transition.NextScene = kv.Key; transition.SavedState = kv.Value; stackedScenes.RemoveAt(stackedScenes.Count - 1); } } CurrentTransition = transition; //场景类和其数据类共同组成了transition var nextScene = transition.NextScene; var savedState = transition.SavedState; //别忘了在Iscene中定义的状态!在取出栈中场景后,销毁当前场景前,先调用pause currentScene?.Pause(); Push

对于push,要将当前场景和需要保存的场景数据压入栈。

if (transition.TransitionType == TransitionType.Push && currentScene != null) { stackedScenes.Add(new KeyValuePair(currentScene, nullSavedState)); } currentScene?.Pause();

压入栈中时,随场景一起压入的是一个空数据,此数据将在场景建立完成后再被填充。由于一般来说,我们需要保存场景在被销毁前的最后一个状态,因此,最合理的方式应该是在IScene.Finish()函数中将数据存入(还记得上面的IScene状态机吗),关于数据的形式不会再本篇文章中阐述。显然,对于每个场景,需要保存的数据种类相差甚远,因此对于每个继承自IScene的场景类,编写一个对于该场景的data类是合理的。比如,对于游戏大厅场景LobbyScene :IScene,写一个LobbySceneData来存储它的数据。这个类就是IScene在加载场景时,load函数所获取的参数!

IEnumerator Load(object savedState);

在导入新场景之后,就可以调用当前场景的finish函数来销毁它,并且加载现在的场景了。编写符合需求的函数来完成场景转换的动画,比如用遮罩来实现渐入渐出、或者canvas上的"loading"字样,不在这里赘述。

//统一场景转换动画? currentScene?.Finish(); //统一场景转换动画? currentScene = nextScene; StartCoroutine(currentScene.Load(currentScene.data)); //... currentScene?.Begin(); //.. currentScene?.Resume(); //..

综上,我们就实现了一个最简单的场景管理器!以LobbyScene为例,这一套场景管理的关系如下所示

继承 组合 继承 组合 组合 使用 IScene LobbyScene LobbyData ISceneManager UISceneManager Transition

更新时间:2022.7.16



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3